/*
 * md2.c -- md2 model source file
 *
 * Copyright (c) 2005-2007 David HENRY
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

#include "md2.h"

/* Table of precalculated normals */
static mvec3_t anorms_table_md2[162] = {
#include "anorms.h"
};

/**
 * Load an MD2 model from file.
 *
 * Note: MD2 format stores model's data in little-endian ordering.  On
 * big-endian machines, you'll have to perform proper conversions.
 */
int gsubReadMD2Model(const char *filename, md2_model_t *mdl) {
    FILE *fp;
    int i;

    fp = fopen(filename, "rb");
    if (!fp) {
        MGPI_PostMessagef("readMD2Model: couldn't open \"%s\"", filename);
        return 0;
    }

    /* Read header */
    fread(&mdl->header, 1, sizeof (struct _md2_header), fp);

    if ((mdl->header.ident != 844121161) ||
            (mdl->header.version != 8)) {
        /* Error! */
        MGPI_PostMessagef("readMD2Model: bad version or identifier");
        fclose(fp);
        return 0;
    }

    /* Memory allocations */
    mdl->skins = (struct _md2_skin *)
            malloc(sizeof (struct _md2_skin) * mdl->header.num_skins);
    mdl->texcoords = (struct _md2_texCoord *)
            malloc(sizeof (struct _md2_texCoord) * mdl->header.num_st);
    mdl->triangles = (struct _md2_triangle *)
            malloc(sizeof (struct _md2_triangle) * mdl->header.num_tris);
    mdl->frames = (struct _md2_frame *)
            malloc(sizeof (struct _md2_frame) * mdl->header.num_frames);
    mdl->glcmds = (int *) malloc(sizeof (int) * mdl->header.num_glcmds);
    mdl->vertex_attribute = NULL;

    /* Read model data */
    fseek(fp, mdl->header.offset_skins, SEEK_SET);
    fread(mdl->skins, sizeof (struct _md2_skin),
            mdl->header.num_skins, fp);

    fseek(fp, mdl->header.offset_st, SEEK_SET);
    fread(mdl->texcoords, sizeof (struct _md2_texCoord),
            mdl->header.num_st, fp);

    fseek(fp, mdl->header.offset_tris, SEEK_SET);
    fread(mdl->triangles, sizeof (struct _md2_triangle),
            mdl->header.num_tris, fp);

    fseek(fp, mdl->header.offset_glcmds, SEEK_SET);
    fread(mdl->glcmds, sizeof (int), mdl->header.num_glcmds, fp);

    /* Read frames */
    fseek(fp, mdl->header.offset_frames, SEEK_SET);
    for (i = 0; i < mdl->header.num_frames; ++i) {
        /* Memory allocation for vertices of this frame */
        mdl->frames[i].verts = (struct _md2_vertex *)
                malloc(sizeof (struct _md2_vertex) * mdl->header.num_vertices);

        /* Read frame data */
        fread(mdl->frames[i].scale, sizeof (mvec3_t), 1, fp);
        fread(mdl->frames[i].translate, sizeof (mvec3_t), 1, fp);
        fread(mdl->frames[i].name, sizeof (char), 16, fp);
        fread(mdl->frames[i].verts, sizeof (struct _md2_vertex),
                mdl->header.num_vertices, fp);
    }

    fclose(fp);
    return 1;
}

/**
 * Free resources allocated for the model.
 */
void gsubFreeMD2Model(md2_model_t *mdl) {
    int i;

    if (mdl->skins) {
        free(mdl->skins);
        mdl->skins = NULL;
    }

    if (mdl->texcoords) {
        free(mdl->texcoords);
        mdl->texcoords = NULL;
    }

    if (mdl->triangles) {
        free(mdl->triangles);
        mdl->triangles = NULL;
    }

    if (mdl->glcmds) {
        free(mdl->glcmds);
        mdl->glcmds = NULL;
    }

    if (mdl->frames) {
        for (i = 0; i < mdl->header.num_frames; ++i) {
            free(mdl->frames[i].verts);
            mdl->frames[i].verts = NULL;
        }

        free(mdl->frames);
        mdl->frames = NULL;
    }
}

/**
 * Render the model at frame n.
 */
void gsubRenderMD2Frame(int n, const md2_model_t *mdl) {
    int i, j, tex = 0;
    GLfloat s, t;
    mvec3_t v;
    struct _md2_frame *pframe;
    struct _md2_vertex *pvert;

    /* Check if n is in a valid range */
    if ((n < 0) || (n > mdl->header.num_frames - 1))
        return;

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    /* Draw the model */
    glBegin(GL_TRIANGLES);
    {
        while (tex < mdl->header.num_skins) {
            glBindTexture(GL_TEXTURE_2D, mdl->tex_ids[tex]);

            /* Draw each triangle */
            for (i = 0; i < mdl->header.num_tris; ++i) {
                /* Draw each vertex */
                for (j = 0; j < 3; ++j) {
                    if (mdl->vertex_attribute) mdl->vertex_attribute(mdl);

                    pframe = &mdl->frames[n];
                    pvert = &pframe->verts[mdl->triangles[i].vertex[j]];

                    /* Compute texture coordinates */
                    s = (GLfloat) mdl->texcoords[mdl->triangles[i].st[j]].s / mdl->header.skinwidth;
                    t = (GLfloat) mdl->texcoords[mdl->triangles[i].st[j]].t / mdl->header.skinheight;

                    /* Pass texture coordinates to OpenGL */
                    glTexCoord2f(s, t);

                    /* Normal vector */
                    glNormal3fv(anorms_table_md2[pvert->normalIndex]);

                    /* Calculate vertex real position */
                    v[0] = (pframe->scale[0] * pvert->v[0]) + pframe->translate[0];
                    v[1] = (pframe->scale[1] * pvert->v[1]) + pframe->translate[1];
                    v[2] = (pframe->scale[2] * pvert->v[2]) + pframe->translate[2];
                    /*printf("v = { %2.2f, %2.2f, %2.2f }\n", v[0], v[1], v[2]);*/
                    glVertex3fv(v);
                }
            }
            ++tex;
        }
    }
    glEnd();

    glDisable(GL_BLEND);
}

/**
 * Render the model with interpolation between frame n and n+1.
 * interp is the interpolation percent. (from 0.0 to 1.0)
 */
void gsubRenderMD2FrameItp(int n, float interp, const md2_model_t *mdl) {
    int i, j, tex = 0;
    GLfloat s, t;
    mvec3_t v_curr, v_next, v, norm;
    float *n_curr, *n_next;
    struct _md2_frame *pframe1, *pframe2;
    struct _md2_vertex *pvert1, *pvert2;

    /* Check if n is in a valid range */
    if ((n < 0) || (n > mdl->header.num_frames))
        return;

    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    /* Draw the model */
    glBegin(GL_TRIANGLES);
    {
        while (tex < mdl->header.num_skins) {
            glBindTexture(GL_TEXTURE_2D, mdl->tex_ids[tex]);

            /* Draw each triangle */
            for (i = 0; i < mdl->header.num_tris; ++i) {
                /* Draw each vertex */
                for (j = 0; j < 3; ++j) {
                    if (mdl->vertex_attribute) mdl->vertex_attribute(mdl);

                    pframe1 = &mdl->frames[n];
                    pframe2 = &mdl->frames[n + 1];
                    pvert1 = &pframe1->verts[mdl->triangles[i].vertex[j]];
                    pvert2 = &pframe2->verts[mdl->triangles[i].vertex[j]];

                    /* Compute texture coordinates */
                    s = (GLfloat) mdl->texcoords[mdl->triangles[i].st[j]].s / mdl->header.skinwidth;
                    t = (GLfloat) mdl->texcoords[mdl->triangles[i].st[j]].t / mdl->header.skinheight;

                    /* Pass texture coordinates to OpenGL */
                    glTexCoord2f(s, t);

                    /* Interpolate normals */
                    n_curr = anorms_table_md2[pvert1->normalIndex];
                    n_next = anorms_table_md2[pvert2->normalIndex];

                    norm[0] = n_curr[0] + interp * (n_next[0] - n_curr[0]);
                    norm[1] = n_curr[1] + interp * (n_next[1] - n_curr[1]);
                    norm[2] = n_curr[2] + interp * (n_next[2] - n_curr[2]);

                    glNormal3fv(norm);

                    /* Interpolate vertices */
                    v_curr[0] = pframe1->scale[0] * pvert1->v[0] + pframe1->translate[0];
                    v_curr[1] = pframe1->scale[1] * pvert1->v[1] + pframe1->translate[1];
                    v_curr[2] = pframe1->scale[2] * pvert1->v[2] + pframe1->translate[2];

                    v_next[0] = pframe2->scale[0] * pvert2->v[0] + pframe2->translate[0];
                    v_next[1] = pframe2->scale[1] * pvert2->v[1] + pframe2->translate[1];
                    v_next[2] = pframe2->scale[2] * pvert2->v[2] + pframe2->translate[2];

                    v[0] = v_curr[0] + interp * (v_next[0] - v_curr[0]);
                    v[1] = v_curr[1] + interp * (v_next[1] - v_curr[1]);
                    v[2] = v_curr[2] + interp * (v_next[2] - v_curr[2]);

                    glVertex3fv(v);
                }
            }
            ++tex;
        }
    }
    glEnd();

    glDisable(GL_BLEND);
}

/**
 * Render the model at frame n using model's GL command list.
 */
void gsubRenderMD2FrameWithGLCmds(int n, const md2_model_t *mdl) {
    int i, *pglcmds, tex = 0;
    mvec3_t v;
    struct _md2_frame *pframe;
    struct _md2_vertex *pvert;
    struct _md2_glcmd *packet;

    /* Check if n is in a valid range */
    if ((n < 0) || (n > mdl->header.num_frames - 1))
        return;

    /* pglcmds points at the start of the command list */
    pglcmds = mdl->glcmds;

    /* Draw the model */
    while ((i = *(pglcmds++)) != 0) {
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        while (tex < mdl->header.num_skins) {
            glBindTexture(GL_TEXTURE_2D, mdl->tex_ids[tex]);

            if (i < 0) {
                glBegin(GL_TRIANGLE_FAN);
                i = -i;
            } else {
                glBegin(GL_TRIANGLE_STRIP);
            }
            {
                /* Draw each vertex of this group */
                for (/* Nothing */; i > 0; --i, pglcmds += 3) {
                    if (mdl->vertex_attribute) mdl->vertex_attribute(mdl);

                    packet = (struct _md2_glcmd *) pglcmds;
                    pframe = &mdl->frames[n];
                    pvert = &pframe->verts[packet->index];

                    /* Pass texture coordinates to OpenGL */
                    glTexCoord2f(packet->s, packet->t);

                    /* Normal vector */
                    glNormal3fv(anorms_table_md2[pvert->normalIndex]);

                    /* Calculate vertex real position */
                    v[0] = (pframe->scale[0] * pvert->v[0]) + pframe->translate[0];
                    v[1] = (pframe->scale[1] * pvert->v[1]) + pframe->translate[1];
                    v[2] = (pframe->scale[2] * pvert->v[2]) + pframe->translate[2];

                    glVertex3fv(v);
                }
            }
            glEnd();
            ++tex;
        }
        glDisable(GL_BLEND);
    }
}

/**
 * Render the model with interpolation between frame n and n+1
 * using model's GL command list.
 * interp is the interpolation percent. (from 0.0 to 1.0)
 */
void gsubRenderMD2FrameItpWithGLCmds(int n, float interp, const md2_model_t *mdl) {
    int i, *pglcmds, tex = 0;
    mvec3_t v_curr, v_next, v, norm;
    float *n_curr, *n_next;
    struct _md2_frame *pframe1, *pframe2;
    struct _md2_vertex *pvert1, *pvert2;
    struct _md2_glcmd *packet;

    /* Check if n is in a valid range */
    if ((n < 0) || (n > mdl->header.num_frames - 1))
        return;

    /* pglcmds points at the start of the command list */
    pglcmds = mdl->glcmds;

    /* Draw the model */
    while ((i = *(pglcmds++)) != 0) {
        glEnable(GL_BLEND);
        glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

        while (tex < mdl->header.num_skins) {
            glBindTexture(GL_TEXTURE_2D, mdl->tex_ids[tex]);/* TODO: this out from here!!! */

            if (i < 0) {
                glBegin(GL_TRIANGLE_FAN);
                i = -i;
            } else {
                glBegin(GL_TRIANGLE_STRIP);
            }
            {
                /* Draw each vertex of this group */
                for (/* Nothing */; i > 0; --i, pglcmds += 3) {
                    if (mdl->vertex_attribute) mdl->vertex_attribute(mdl);

                    packet = (struct _md2_glcmd *) pglcmds;
                    pframe1 = &mdl->frames[n];
                    pframe2 = &mdl->frames[n + 1];
                    pvert1 = &pframe1->verts[packet->index];
                    pvert2 = &pframe2->verts[packet->index];

                    /* Pass texture coordinates to OpenGL */
                    glTexCoord2f(packet->s, packet->t);

                    /* Interpolate normals */
                    n_curr = anorms_table_md2[pvert1->normalIndex];
                    n_next = anorms_table_md2[pvert2->normalIndex];

                    norm[0] = n_curr[0] + interp * (n_next[0] - n_curr[0]);
                    norm[1] = n_curr[1] + interp * (n_next[1] - n_curr[1]);
                    norm[2] = n_curr[2] + interp * (n_next[2] - n_curr[2]);

                    glNormal3fv(norm);

                    /* Interpolate vertices */
                    v_curr[0] = pframe1->scale[0] * pvert1->v[0] + pframe1->translate[0];
                    v_curr[1] = pframe1->scale[1] * pvert1->v[1] + pframe1->translate[1];
                    v_curr[2] = pframe1->scale[2] * pvert1->v[2] + pframe1->translate[2];

                    v_next[0] = pframe2->scale[0] * pvert2->v[0] + pframe2->translate[0];
                    v_next[1] = pframe2->scale[1] * pvert2->v[1] + pframe2->translate[1];
                    v_next[2] = pframe2->scale[2] * pvert2->v[2] + pframe2->translate[2];

                    v[0] = v_curr[0] + interp * (v_next[0] - v_curr[0]);
                    v[1] = v_curr[1] + interp * (v_next[1] - v_curr[1]);
                    v[2] = v_curr[2] + interp * (v_next[2] - v_curr[2]);

                    glVertex3fv(v);
                }
            }
            glEnd();
            ++tex;
        }
        glDisable(GL_BLEND);
    }
}

/**
 * Calculate the current frame in animation beginning at frame
 * 'start' and ending at frame 'end', given interpolation percent.
 * interp will be reseted to 0.0 if the next frame is reached.
 */
void gsubAnimateMD2(int start, int end, int *frame, float *interp) {
    if ((*frame < start) || (*frame > end))
        *frame = start;

    if (*interp >= 1.0f) {
        /* Move to next frame */
        *interp = 0.0f;
        (*frame)++;

        if (*frame >= end)
            *frame = start;
    }
}
